// 'use strict'

// <--    还原前    -->
var a = {
    "YJJox": "object",
    "sbTga": function (b, c) {return b | c;},
    "iwvEK": function (b, c) {return b << c;},
    "HqkiD": function (b, c) {return b(c);},
    };
b = a["iwvEK"](1, 3),c = a["sbTga"](111,222),d = a["YJJox"],e = a["HqkiD"](String.fromCharCode,49);
/**
 * <--    特点    -->
 * 数据由一个对象通过属性保存，后续使用的时候，通过访问对象属性名来实现。跟用数组来保存数据并用序号来访问是一个道理。
 * 如果是将对象属性的访问方式消解，直接用属性值来替换，就会很容易阅读。
 * 同时如果属性值为函数，发现是纯函数，且内部都是二元计算,可以直接将iife转换为表达式
 * 
 * <--    解决办法    -->
 * 1. 对象属性引用消解，还原为字面量或立即调用函数表达式
 * 2. 立即调用表达式FunctionExpression.params、CallExpression.arguments消解
 * 3. iife单ReturnStatement语句-expression的消解
 * 3. 字面量表达式常量折叠
 * 
 * <--    冗余判断    -->
 * 1. 对象没有被重新赋值过
 * 2. iife计算时的分类
 **/

// <--    还原后（第一步，对象属性引用消解）    -->
b = (function (b, c) {
    return b << c;
  })(1, 3), c = (function (b, c) {
    return b | c;
  })(111, 222), d = "object", e = (function (b, c) {
    return b(c);
  })(String.fromCharCode, 49);

// <--    还原后（第二步，iife形参实参消解）    -->
b = (function () {
    return 1 << 3;
  })(), c = (function () {
    return 111 | 222;
  })(), d = "object", e = (function () {
    return String.fromCharCode(49);
  })();

// <--    还原后（第三步，iife无参但return语句转换成表达式）    -->
b = (1 << 3), c = (111 | 222), d = "object", e = String.fromCharCode(49);

// <--    还原后（第三步，字面量折叠）    -->
b = 8, c = 255, d = "object", e = "1";


const {parse} = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;

let xcode = `var a = {
    "YJJox": "object",
    "sbTga": function (b, c) {return b | c;},
    "iwvEK": function (b, c) {return b << c;},
    "HqkiD": function (b, c) {return b(c);},
    };
b = a["iwvEK"](1, 3),c = a["sbTga"](111,222),d = a["YJJox"],e = a["HqkiD"](String.fromCharCode,49);`

const visitor = {
    VariableDeclarator:{
        // 对象属性引用消解
        enter:function(path){
            // path.scope.dump();
            // 初始值是否为对象表达式，且本变量没有被重新赋值过，且存在引用
            if(!t.isObjectExpression(path.get('init').node)){return}
            let binding = path.scope.getBinding(path.get('id').node.name);
            if(!binding || !binding.constant || !binding.referenced){return}
            
            let objExpression = path.get('init');
            // 枚举出所有引用的地方
            for(let refPath of binding.referencePaths){
                if(!t.isMemberExpression(refPath.parent)){continue}
                
                let membExpression = refPath.parentPath;
                // 获得属性名
                let attrName = membExpression.get('property').node.value;
                // 在对象表达式中遍历出本属性所在
                objExpression.traverse({
                    ObjectProperty:function(path){
                        if(path.get('key').node.value !== attrName){return}
                        let attrValNode = path.get('value').node;
                        if(t.isFunctionExpression(attrValNode)){
                            attrValNode = t.parenthesizedExpression(attrValNode);
                        }
                        // 将引用的对象成员表达式消解
                        membExpression.replaceInline(attrValNode);
                    },
                });
            }
            path.scope.crawl();
            // path.scope.dump();
            binding = path.scope.getBinding(path.get('id').node.name);
            if(binding && binding.constant && !binding.referenced){
                path.remove();
            }
            path.scope.crawl();
        },
    },

    CallExpression:{
        enter:[
            // iife 形参实参消解
            function(path){
                if(!t.isFunctionExpression(path.get('callee.expression').node) || path.get('arguments').length == 0){return}
                let params = path.get('callee.expression.params'), args = path.get('arguments');
                for (let i in params){
                    let param = params[i], arg = args[i];
                    let binding = param.scope.getBinding(param.node.name);
                    if (binding && binding.constant){
                        let argNode = arg.node;
                        for (let refer of binding.referencePaths){
                            if (t.isBinaryExpression(argNode)){
                                argNode = t.parenthesizedExpression(argNode)
                            }
                            refer.replaceInline(argNode);
                        }
                    }
                    param.scope.crawl();
                    binding = param.scope.getBinding(param.node.name);
                    if(binding && binding.constant && !binding.referenced){
                        arg.remove();
                        param.remove();
                    }

                }
                path.scope.crawl();
            },
            // iife 空参数，单个return语句还原为表达式
            function(path){
                if(!t.isFunctionExpression(path.get('callee.expression').node)
                    || path.get('arguments').length > 0
                    || path.get('callee.expression.body.body').length != 1){return}
                let rtnStmt = path.get('callee.expression.body.body.0');
                let exp;
                if(t.isReturnStatement(rtnStmt)){
                    let argNode = rtnStmt.get('argument').node;
                    if (t.isBinaryExpression(argNode)){
                        argNode = t.parenthesizedExpression(argNode);
                    }
                    exp = path.replaceInline(argNode);
                }
                if(exp && exp.scope){
                    exp.scope.crawl()
                }
            },
            // CallExpression字面量折叠
            function(path){
                let {confident, value} = path.evaluate();
                if(confident){
                    path.replaceInline(t.valueToNode(value));
                }
                if(path.scope){
                    path.scope.crawl();
                }
            },
        ]
    },

    'BinaryExpression|ConditionalExpression|MemberExpression|SequenceExpression|ParenthesizedExpression|UnaryExpression':{
        // 字面量折叠
        enter:function(path){
            let {confident, value} = path.evaluate();
            if(confident){
                path.replaceInline(t.valueToNode(value));
            }
        },
    },
}

let ast = parse(xcode);
traverse(ast, visitor);
let {code} = generator(ast);
console.log(code);
